#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <json/json.h>

#include "command.h"
#include "base.h"
#include "json.h"
#include "error_code.h"
#include "hbase_client.h"
#include "connpool.h"
#include "datainit.h"
#include "http_echo.h"
#include "fcgi_stdio.h"
#include "hd_fs_operation.h"
#include "async_fs_operator.h"
#include "threadpool.h"

#define valid_char(c) ( \
((c) <= '9' && (c) >= '0') || \
((c) <= 'z' && (c) >= 'a') || \
((c) <= 'Z' && (c) >= 'A') || \
((c) == '-') || \
((c) == '_') \
)

using namespace std;
#define COMPRESSION 1

//int index_append(char *prefix, char *index, char *val, char *rowkey);
extern HadoopFSOperation* g_fsops;
extern AsyncFSOperator<HadoopFSOperation, ThreadPool> *g_async_fsops;

void CloseRequest(FCGX_Request *request)
{
    DINFO("close request:%p", request);
    FCGX_Finish_r(request);
    delete request;
}

HResult cmd_mkdir(FCGX_Request *request, DIR_ID parentID, const char* dirName)
{
    g_async_fsops->CreateNewDirAsync(parentID, string(dirName), [=] (HResult ret, DIR_INFO&& info)
    {
        if (ret != ERROR_CODE_OK)
        {
            char errbuf[1024];
            dp_strerror(ret, errbuf, 1024);
            DERROR("create dir <%llX:%s> failed: <%s>", parentID._id, dirName, errbuf);
            http_echo_error(request, 409, ret);
        }
        else
        {
            DNOTE("create dir <%llX:%s> complete", parentID._id, dirName);
            Json::Value root;
            Json::Value item;
            get_json(info, item);
            root["result"] = ret;
            root["data"] = item;
            string jstr = root.toStyledString();
            http_echo(request, 200, jstr.c_str(), jstr.size());
        }
        
        CloseRequest(request);
    });
    
    return ERROR_CODE_OK;
}

HResult cmd_lsdir(FCGX_Request *request, DIR_ID parentID, int mode)
{
    Json::Value *root = new Json::Value();
    Json::Value *arrayObj = new Json::Value();

    DINFO("request=%p, mode=%d", request, mode);
    HResult ret = ERROR_CODE_OK;
    
    if ((mode & LIST_DIR) != 0)    
    {
        g_async_fsops->GetSubdirInfosAsync(parentID, [=] (HResult hr, std::vector<DIR_INFO>&& dirInfos) {
            DINFO("request=%p, mode=%d", request, mode);
            int dircount = 0;
            if (hr == ERROR_CODE_OK)
            {
                Json::Value item;
                for (size_t idx = 0; idx < dirInfos.size(); idx ++)
                {
                    get_json(dirInfos[idx], item);
                    arrayObj->append(item);
                }

                dircount = (int)dirInfos.size();
            }
            else
            {
                DERROR("List DIR failed, err=%d", hr);
                http_echo_error(request, 409, hr);
                CloseRequest(request);
                delete root;
                delete arrayObj;
                return;
            }
            
            if ((mode & LIST_FILE) != 0)
            {
                 g_async_fsops->GetSubfileInfosAsync(parentID, [=] (HResult hr, std::vector<FILE_INFO>&& fileInfos) {
                     DINFO("request=%p, mode=%d", request, mode);
                     int filecount = 0;
                     if (hr == ERROR_CODE_OK)
                     {
                          Json::Value item;
                          for (size_t idx = 0; idx < fileInfos.size(); idx ++)
                          {
                              get_json(fileInfos[idx], item);
                              arrayObj->append(item);
                          }

                          filecount = (int) fileInfos.size();
                     }
                     else
                     {
                          DERROR("List FILE failed, err=%d", hr);
                          http_echo_error(request, 409, hr);
                          CloseRequest(request);
                          delete root;
                          delete arrayObj;
                          return;
                     }
 
                     (*root)["array"] = *arrayObj;
                     (*root)["count"] = dircount + filecount;
    
                     string jstr = root->toStyledString();
    
                     DINFO("json string: %s\n", jstr.c_str());
      
                     http_echo(request, 200, jstr.c_str(), jstr.size());
                     
                     CloseRequest(request);
                     delete root;
                     delete arrayObj;
                 });
            }
            else
            {
                (*root)["array"] = *arrayObj;
                (*root)["count"] = dircount;
    
                string jstr = root->toStyledString();
                
                DINFO("json string: %s\n", jstr.c_str());
                
                http_echo(request, 200, jstr.c_str(), jstr.size());
                CloseRequest(request);
                delete root;
                delete arrayObj;
            }
        });
        return ret;
    }
    
    if ((mode & LIST_FILE) != 0)
    {
        g_async_fsops->GetSubfileInfosAsync(parentID, [=] (HResult hr, std::vector<FILE_INFO>&& fileInfos) {
            int filecount;
            if (hr == ERROR_CODE_OK)
            {
                Json::Value item;
                for (size_t idx = 0; idx < fileInfos.size(); idx ++)
                {
                    get_json(fileInfos[idx], item);
                    arrayObj->append(item);
                }

                filecount = (int) fileInfos.size();
            }
            else
            {
                DERROR("List FILE failed, err=%d", hr);
                http_echo_error(request, 409, ret);
                FCGX_Finish_r(request);
                delete root;
                delete arrayObj;
                return;
            }
     
            (*root)["array"] = *arrayObj;
            (*root)["count"] = filecount;
        
            string jstr = root->toStyledString();

            DINFO("json string: %s\n", jstr.c_str());
        
            http_echo(request, 200, jstr.c_str(), jstr.size());
            FCGX_Finish_r(request);
            delete root;
            delete arrayObj;
        });
    }

    return ret;
}

HResult cmd_rmdir(FCGX_Request *request, DIR_ID parentID, const char* dirName)
{
    g_async_fsops->DeleteDirAsync(parentID, string(dirName), [=] (HResult ret)
           {
                if (ret != ERROR_CODE_OK)
                {
                    char errbuf[1024];
                    dp_strerror(ret, errbuf, 1024);
                    DERROR("remove dir <%llX:%s> failed: <%s>", parentID._id, dirName, errbuf);
                    http_echo_error(request, 409, ret);
                }
                else
                {
                    DNOTE("remove dir <%llX:%s> complete", parentID._id, dirName);
                    http_echo_error(request, 200, ret);
                }
                
                CloseRequest(request);
            });
    
    return ERROR_CODE_OK;
}

HResult cmd_rename_dir(FCGX_Request *request, DIR_ID parentID, const char* dirName, const char* newdirname)
{
    g_async_fsops->RenameDirAsync(parentID, string(dirName), string(newdirname), [=] (HResult ret, DIR_INFO&& info)
                                         {
                                             if (ret != ERROR_CODE_OK)
                                             {
                                                 char errbuf[1024];
                                                 dp_strerror(ret, errbuf, 1024);
                                                 DERROR("rename dir <%llX:%s:%s> failed: <%s>", parentID._id, dirName, newdirname, errbuf);
                                                 http_echo_error(request, 409, ret);
                                             }
                                             else
                                             {
                                                 DNOTE("rename dir <%llX:%s:%s> complete", parentID._id, dirName, newdirname);
                                                 Json::Value root;
                                                 Json::Value item;
                                                 get_json(info, item);
                                                 root["result"] = ret;
                                                 root["data"] = item;
                                                 string jstr = root.toStyledString();
                                                 http_echo(request, 200, jstr.c_str(), jstr.size());
                                             }
                                             
                                             CloseRequest(request);
                                         });
    return ERROR_CODE_OK;
}

HResult cmd_writefile(FCGX_Request *request, DIR_ID parentID, const char* fileName, const char* data, uint32_t datalen)
{
    g_async_fsops->CreateNewFileAsync(parentID, string(fileName), (const uint8_t*)data, datalen, [=] (HResult ret, FILE_INFO&& info)
                                         {
                                             if (ret != ERROR_CODE_OK)
                                             {
                                                 char errbuf[1024];
                                                 dp_strerror(ret, errbuf, 1024);
                                                 DERROR("write file <%llX:%s> failed: <%s>", parentID._id, fileName, errbuf);
                                                 http_echo_error(request, 409, ret);
                                             }
                                             else
                                             {
                                                 DNOTE("write file <%llX:%s> complete", parentID._id, fileName);
                                                 Json::Value root;
                                                 Json::Value item;
                                                 get_json(info, item);
                                                 root["result"] = ret;
                                                 root["data"] = item;
                                                 string jstr = root.toStyledString();
                                                 http_echo(request, 200, jstr.c_str(), jstr.size());
                                             }
                                             
                                             CloseRequest(request);
                                         });
    return ERROR_CODE_OK;
}

//TODO: change to async interface
HResult cmd_readfile(FCGX_Request *request, DIR_ID parentID, const char* fileName, uint32_t version, uint32_t offset, uint32_t length)
{
    HResult ret = ERROR_CODE_OK;
    
    DINFO("read file, name=%s, offset=%u, length=%u", fileName, offset, length);
    
    FILE_INFO info;
    if (version != (uint32_t)-1)
    {
        ret = g_fsops->GetSubfileInfo(parentID, fileName, version, info);
    }
    else
    {
        ret = g_fsops->GetSubfileInfo(parentID, fileName, info);
    }
    
    if (ret != ERROR_CODE_OK)
    {
        DERROR("Get file info for %s failed, err:%d", fileName, ret);
        return ret;
    }
    DINFO("File Length:%u", info._length);
    if (length == (uint32_t)-1 || length > info._length)
    {
        length = info._length;
    }
    DINFO("Read Length:%u", length);

    char* buf = new char[length + 1];
    memset(buf, 0, length + 1);
    size_t actuallen;
    ret = g_fsops->ReadFile(info, offset, (int8_t*)buf, length, actuallen);
    
    DINFO("read file complete, name=%s, offset=%u, length=%u, actuallen=%u", fileName, offset, length, actuallen);
    
    if (ret == ERROR_CODE_OK)
    {
        buf[actuallen] = '\0';
        http_echo_attachment(request, 200, buf, actuallen); 
    }
    else
    {
        DERROR("Read file data for %s failed, err:%d", fileName, ret);
    }
    delete buf;
    return ret;
}

HResult cmd_rmfile(FCGX_Request *request, DIR_ID parentID, const char* fileName)
{
    g_async_fsops->DeleteFileAsync(parentID, string(fileName), [=] (HResult ret)
                                             {
                                                 if (ret != ERROR_CODE_OK)
                                                 {
                                                     char errbuf[1024];
                                                     dp_strerror(ret, errbuf, 1024);
                                                     DERROR("remove file <%llX:%s> failed: <%s>", parentID._id, fileName, errbuf);
                                                     http_echo_error(request, 409, ret);
                                                 }
                                                 else
                                                 {
                                                     DNOTE("remove file <%llX:%s> complete", parentID._id, fileName);
                                                     http_echo_error(request, 200, ret);
                                                 }
                                                 
                                                 CloseRequest(request);
                                             });
    return ERROR_CODE_OK;
}

HResult cmd_rename_file(FCGX_Request *request, DIR_ID parentID, const char* fileName, const char* newFileName)
{
    g_async_fsops->RenameFileAsync(parentID, string(fileName), string(newFileName), [=] (HResult ret, FILE_INFO&& info)
                                             {
                                                 if (ret != ERROR_CODE_OK)
                                                 {
                                                     char errbuf[1024];
                                                     dp_strerror(ret, errbuf, 1024);
                                                     DERROR("rename file <%llX:%s:%s> failed: <%s>", parentID._id, fileName, newFileName, errbuf);
                                                     http_echo_error(request, 409, ret);
                                                 }
                                                 else
                                                 {
                                                     DNOTE("rename file <%llX:%s:%s> complete", parentID._id, fileName, newFileName);
                                                     Json::Value root;
                                                     Json::Value item;
                                                     get_json(info, item);
                                                     root["result"] = ret;
                                                     root["data"] = item;
                                                     string jstr = root.toStyledString();
                                                     http_echo(request, 200, jstr.c_str(), jstr.size());
                                                 }
                                                 
                                                 CloseRequest(request);
                                             });
    return ERROR_CODE_OK;
}

//TODO: change to async interface
HResult cmd_recover(DIR_ID parentID, const char* name, int type)
{
    HResult ret = ERROR_CODE_OK;
    switch (type) {
        case 1: //DIR
            ret = g_fsops->RecoverDir(parentID, string(name));
            break;
        case 2: //FILE
            ret = g_fsops->RecoverFile(parentID, string(name));
            break;            
        default:
            ret = ERROR_CODE_ILLEGAL_ARGUMENT;
            break;
    }
    
    return ret;
}

HResult cmd_dirinfo(FCGX_Request *request, DIR_ID parentID, const char* dirName)
{
    g_async_fsops->GetSubdirInfoAsync(parentID, string(dirName), [=] (HResult ret, DIR_INFO&& info)
                                          {
                                              if (ret != ERROR_CODE_OK)
                                              {
                                                  char errbuf[1024];
                                                  dp_strerror(ret, errbuf, 1024);
                                                  DERROR("get dir info <%llX:%s> failed: <%s>", parentID._id, dirName, errbuf);
                                                  http_echo_error(request, 409, ret);
                                              }
                                              else
                                              {
                                                  DNOTE("get dir info <%llX:%s> complete", parentID._id, dirName);
                                                  Json::Value root;
                                                  Json::Value item;
                                                  get_json(info, item);
                                                  root["result"] = ret;
                                                  root["data"] = item;
                                                  string jstr = root.toStyledString();
                                                  http_echo(request, 200, jstr.c_str(), jstr.size());
                                              }
                                              
                                              CloseRequest(request);
                                          });
    return ERROR_CODE_OK;
}

HResult cmd_fileinfo(FCGX_Request *request, DIR_ID parentID, const char* fileName)
{
    g_async_fsops->GetSubfileInfoAsync(parentID, string(fileName), [=] (HResult ret, FILE_INFO&& info)
                                          {
                                              if (ret != ERROR_CODE_OK)
                                              {
                                                  char errbuf[1024];
                                                  dp_strerror(ret, errbuf, 1024);
                                                  DERROR("get file info <%llX:%s> failed: <%s>", parentID._id, fileName, errbuf);
                                                  http_echo_error(request, 409, ret);
                                              }
                                              else
                                              {
                                                  DNOTE("get file info <%llX:%s> complete", parentID._id, fileName);
                                                  Json::Value root;
                                                  Json::Value item;
                                                  get_json(info, item);
                                                  root["result"] = ret;
                                                  root["data"] = item;
                                                  string jstr = root.toStyledString();
                                                  http_echo(request, 200, jstr.c_str(), jstr.size());
                                              }
                                              
                                              CloseRequest(request);
                                          });
    return ERROR_CODE_OK;
}

HResult cmd_fileversion(FCGX_Request *request, DIR_ID parentID, const char* fileName)
{
    HResult ret = ERROR_CODE_OK;
    
    vector<FILE_INFO> fileInfos;
    ret = g_fsops->GetSubfileInfo(parentID, fileName, fileInfos);
    if (ret != ERROR_CODE_OK)
    {
        DERROR("Get file info for %s failed, err:%d", fileName, ret);
        http_echo_error(request, 409, ret);
        CloseRequest(request);
        return ret;
    }
    
    DNOTE("get file version <%llX:%s> complete, version count:%d",
          parentID._id, fileName, (int)fileInfos.size());
    
    Json::Value root;
    Json::Value arrayObj;
    Json::Value item;
    for (size_t idx = 0; idx < fileInfos.size(); idx ++)
    {
        get_json(fileInfos[idx], item);
        arrayObj.append(item);
    }
    root["array"] = arrayObj;
    root["count"] = (int)fileInfos.size();
    
    string jstr = root.toStyledString();
    http_echo(request, 200, jstr.c_str(), jstr.size());
    
    CloseRequest(request);
    
    DNOTE("get file version <%llX:%s> complete");
    return ret;
}

HResult process_cmd(FCGX_Request *request, ParserStruc *pstruc)
{
    if (NULL == pstruc) {
        DERROR("pstruc is null");
        return -1;
    }
        
    char cmd = pstruc->cmd;
    HResult ret = ERROR_CODE_OK;
    
	switch(cmd) {
        case CMD_MKDIR:
        {
            DINFO("<<< cmd mkdir >>>");
            ret = cmd_mkdir(request, pstruc->_parentid, pstruc->_name);
            break;
        }
        case CMD_LSDIR:
        {
            DINFO("<<< cmd lsdir >>>");
            
            int mode;
            ret = parser_cmd_lsdir_param(pstruc, mode);
            if (ret < 0)
            {
                DERROR("Get List DIR Mode failed, will use all as default");
                mode = LIST_DIR | LIST_FILE;
            }
            
            ret = cmd_lsdir(request, pstruc->_parentid, mode);
            break;
        }
        case CMD_RMDIR:
        {
            DINFO("<<< cmd rmdir >>>");
            ret = cmd_rmdir(request, pstruc->_parentid._id, pstruc->_name);
            break;
        }
        case CMD_RENAME_DIR:
        {
            DINFO("<<< cmd rename_dir >>>");
            DIR_INFO info;
			ret = cmd_rename_dir(request, pstruc->_parentid, pstruc->_name, pstruc->_newname);
            break;
        }
        case CMD_WRITEFILE:
        {
            DINFO("<<< cmd writefile >>>\n");
            // get data
            DINFO("Content: %s", pstruc->content);
            ret = cmd_writefile(request, pstruc->_parentid, pstruc->_name, pstruc->content, pstruc->content_length);
            break;
        }
        case CMD_READFILE:
        {
            DINFO("<<< cmd readfile >>>");
            uint32_t offset, length, version;
            ret = parser_cmd_readfile_param(pstruc, version, offset, length);
            if (ret < 0)
            {
                char errbuf[1024];
				dp_strerror(ret, errbuf, 1024);
                DERROR("read file <%llX:%s> failed: <%s>", pstruc->_parentid._id, pstruc->_name, errbuf);
				http_echo_error(request, 409, ret);
                break;
            }
            
            ret = cmd_readfile(request, pstruc->_parentid, pstruc->_name, version, offset, length);
            if (ret < 0)
            {
				char errbuf[1024];
				dp_strerror(ret, errbuf, 1024);
                DERROR("read file <%llX:%s> failed: <%s>", pstruc->_parentid._id, pstruc->_name, errbuf);
				http_echo_error(request, 409, ret);
			}
            else
            {
				DNOTE("read file <%llX:%s> complete", pstruc->_parentid._id, pstruc->_name);
            }
            
            CloseRequest(request);
            break;
        }
        case CMD_RMFILE:
        {
            DINFO("<<< cmd rmfile >>>");
            ret = cmd_rmfile(request, pstruc->_parentid, pstruc->_name);
            break;
        }
        case CMD_RENAME_FILE:
        {
            DINFO("<<< cmd rename_file >>>");
			ret = cmd_rename_file(request, pstruc->_parentid, pstruc->_name, pstruc->_newname);
            break;
        }
        case CMD_RECOVER:
        {
            DINFO("<<< cmd recover >>>");
            int type;
            ret = parser_cmd_recover_param(pstruc, type);            
            if (ret < 0)
            {
                char errbuf[1024];
				dp_strerror(ret, errbuf, 1024);
                DERROR("read file <%llX:%s> failed: <%s>", pstruc->_parentid._id, pstruc->_name, errbuf);
				http_echo_error(request, 409, ret);
                break;
            }
            
            ret = cmd_recover(pstruc->_parentid, pstruc->_name, type);
			if (ret < 0)
            {
				char errbuf[1024];
				dp_strerror(ret, errbuf, 1024);
                DERROR("recover file <%llX:%s:%s> failed: <%s>", pstruc->_parentid._id, pstruc->_name, pstruc->_newname, errbuf);
				http_echo_error(request, 409, ret);
			}
            else
            {
				DNOTE("recover file <%llX:%s:%s> complete", pstruc->_parentid._id, pstruc->_name, pstruc->_newname);
                http_echo_error(request, 200, ret);
			}
            
            CloseRequest(request);
            break;
        }
        case CMD_DIR_INFO:
        {
            DINFO("<<< cmd dirinfo >>>");
			ret = cmd_dirinfo(request, pstruc->_parentid, pstruc->_name);
            break;
        }
        case CMD_FILE_INFO:
        {
            DINFO("<<< cmd fileinfo >>>");
			ret = cmd_fileinfo(request, pstruc->_parentid, pstruc->_name);
            break;
        }
        case CMD_FILE_VERSION:
        {
            DINFO("<<< cmd fileversion >>>");
            ret = cmd_fileversion(request, pstruc->_parentid, pstruc->_name);
            break;
        }
        case CMD_UNKNOWN:
        default:
        {
            DERROR("unknown command <%llX:%s>", pstruc->_parentid._id, pstruc->_name);
            http_echo_error(request, 427, ERROR_CODE_UNKNOWN_COMMAND);
            CloseRequest(request);
            break;
        }
    }
    
    return ret;
}
